//请你为 最不经常使用（LFU）缓存算法设计并实现数据结构。 
//
// 实现 LFUCache 类： 
//
// 
// LFUCache(int capacity) - 用数据结构的容量 capacity 初始化对象 
// int get(int key) - 如果键 key 存在于缓存中，则获取键的值，否则返回 -1 。 
// void put(int key, int value) - 如果键 key 已存在，则变更其值；如果键不存在，请插入键值对。当缓存达到其容量 
//capacity 时，则应该在插入新项之前，移除最不经常使用的项。在此问题中，当存在平局（即两个或更多个键具有相同使用频率）时，应该去除 最近最久未使用 的键。 
// 
//
// 为了确定最不常使用的键，可以为缓存中的每个键维护一个 使用计数器 。使用计数最小的键是最久未使用的键。 
//
// 当一个键首次插入到缓存中时，它的使用计数器被设置为 1 (由于 put 操作)。对缓存中的键执行 get 或 put 操作，使用计数器的值将会递增。 
//
// 函数 get 和 put 必须以 O(1) 的平均时间复杂度运行。 
//
// 
//
// 示例： 
//
// 
//输入：
//["LFUCache", "put", "put", "get", "put", "get", "get", "put", "get", "get", 
//"get"]
//[[2], [1, 1], [2, 2], [1], [3, 3], [2], [3], [4, 4], [1], [3], [4]]
//输出：
//[null, null, null, 1, null, -1, 3, null, -1, 3, 4]
//
//解释：
//// cnt(x) = 键 x 的使用计数
//// cache=[] 将显示最后一次使用的顺序（最左边的元素是最近的）
//LFUCache lfu = new LFUCache(2);
//lfu.put(1, 1);   // cache=[1,_], cnt(1)=1
//lfu.put(2, 2);   // cache=[2,1], cnt(2)=1, cnt(1)=1
//lfu.get(1);      // 返回 1
//                 // cache=[1,2], cnt(2)=1, cnt(1)=2
//lfu.put(3, 3);   // 去除键 2 ，因为 cnt(2)=1 ，使用计数最小
//                 // cache=[3,1], cnt(3)=1, cnt(1)=2
//lfu.get(2);      // 返回 -1（未找到）
//lfu.get(3);      // 返回 3
//                 // cache=[3,1], cnt(3)=2, cnt(1)=2
//lfu.put(4, 4);   // 去除键 1 ，1 和 3 的 cnt 相同，但 1 最久未使用
//                 // cache=[4,3], cnt(4)=1, cnt(3)=2
//lfu.get(1);      // 返回 -1（未找到）
//lfu.get(3);      // 返回 3
//                 // cache=[3,4], cnt(4)=1, cnt(3)=3
//lfu.get(4);      // 返回 4
//                 // cache=[3,4], cnt(4)=2, cnt(3)=3 
//
// 
//
// 提示： 
//
// 
// 1 <= capacity <= 10⁴ 
// 0 <= key <= 10⁵ 
// 0 <= value <= 10⁹ 
// 最多调用 2 * 10⁵ 次 get 和 put 方法 
// 
//
// Related Topics 设计 哈希表 链表 双向链表 👍 700 👎 0


package LeetCode.editor.cn;

import java.util.HashMap;
import java.util.Map;
import java.util.TreeSet;

/**
 * @author ldltd
 * @date 2023-09-25 09:18:57
 * @description 460.LFU 缓存
 */
public class LfuCache{
	 public static void main(String[] args) {
	 	 //测试代码

	 }
	 
//力扣代码
//leetcode submit region begin(Prohibit modification and deletion)
    //hash+平衡二叉树
class LFUCache {
         //  容量  time表示缓存使用时间，
    int capacity,time;
    //缓存索引
    Map<Integer,Node> key_Table;
    //平衡二叉树
    TreeSet<Node> S;
    public LFUCache(int capacity) {
        this.capacity=capacity;
        this.time=0;
        key_Table=new HashMap<Integer,Node>();
        S=new TreeSet<>();
    }
    
    public int get(int key) {
        if(capacity==0) return -1;
        //如果没有  -1
        if(!key_Table.containsKey(key)) return -1;

        Node node = key_Table.get(key);
        //删除节点
        S.remove(node);
        //更新时间和总时间
        node.cnt+=1;
        node.time=++time;
        //重新加入
        S.add(node);
        key_Table.put(key,node);
        return node.value;
    }
    
    public void put(int key, int value) {
        if(capacity==0) return;
        if(!key_Table.containsKey(key)){
            //如果满了，移除最近最少使用的
            if(key_Table.size()==capacity){
                key_Table.remove(S.first().key);
                S.remove(S.first());
            }
            //创建新的
            Node node = new Node(1, ++time, key, value);
            key_Table.put(key,node);
            S.add(node);
        }else {
            Node node = key_Table.get(key);
            S.remove(node);
            node.cnt+=1;
            node.time=++time;
            node.value=value;
            S.add(node);
            key_Table.put(key,node);
        }
    }

}
class Node implements  Comparable<Node>{
        //使用次数，时间，键值
        int cnt,time,key,value;
        Node(int cnt,int time,int key,int value){
            this.cnt=cnt;
            this.time=time;
            this.key=key;
            this.value=value;
        }

        @Override
        public  boolean equals(Object obj){
            if(this==obj){
                return true;
            }
            if(obj instanceof Node){
                Node res=(Node) obj;
                return this.cnt==res.cnt&&this.time==res.time;
            }
            return false;
        }
    @Override
    public int compareTo(Node o) {
        return cnt==o.cnt?time-o.time:cnt-o.cnt;
    }

    @Override
    public int hashCode() {
        return cnt * 1000000007 + time;
    }
}

//双hash表
    class LFUCache1{
            int minfreq,capacity;
            Map<Integer,Node1> keyTable;
            Map<Integer,DoublyLinkedList> freqTable;
        public LFUCache1(int capacity) {
            this.minfreq=0;
            this.capacity=capacity;
            keyTable=new HashMap<>();
            freqTable=new HashMap<>();
        }

        public int get(int key) {
            if(capacity==0) return -1;
            if(!keyTable.containsKey(key)) return -1;
            Node1 node1 = keyTable.get(key);
            int val=node1.value,freq=node1.freq;
            freqTable.get(freq).remove(node1);
            //如果当前链表为空，我们需要在hash中删除，并更新minfreq
            if(freqTable.get(freq).size==0){
                freqTable.remove(freq);
                if(minfreq==freq){
                    minfreq+=1;
                }
            }
            //插入到freq+1中
            DoublyLinkedList list = freqTable.getOrDefault(freq + 1, new DoublyLinkedList());
            list.addFirst(new Node1(key,val,freq+1));
            freqTable.put(freq+1,list);
            keyTable.put(key,freqTable.get(freq+1).getHead());
            return val;
        }

        public void put(int key, int value) {
            if(capacity==0) return ;
            if(!keyTable.containsKey(key)){
                if(keyTable.size()==capacity){
                    Node1 node1 = freqTable.get(minfreq).getTail();
                    keyTable.remove(node1.key);
                    freqTable.get(minfreq).remove(node1);
                    if(freqTable.get(minfreq).size==0){
                        freqTable.remove(minfreq);
                    }
                }

                DoublyLinkedList list = freqTable.getOrDefault(1, new DoublyLinkedList());
                list.addFirst(new Node1(key,value,1));
                freqTable.put(1,list);
                keyTable.put(key,freqTable.get(1).getHead());
                minfreq=1;
            }else {
                // 与 get 操作基本一致，除了需要更新缓存的值
                Node1 node1 = keyTable.get(key);
                int freq = node1.freq;
                freqTable.get(freq).remove(node1);
                if (freqTable.get(freq).size == 0) {
                    freqTable.remove(freq);
                    if (minfreq == freq) {
                        minfreq += 1;
                    }
                }
                DoublyLinkedList list = freqTable.getOrDefault(freq + 1, new DoublyLinkedList());
                list.addFirst(new Node1(key, value, freq + 1));
                freqTable.put(freq + 1, list);
                keyTable.put(key, freqTable.get(freq + 1).getHead());

            }
        }

    }
    class Node1{
         int key,value,freq;
         Node1 pre,next;
         Node1(){
             this(-1,-1,0);
         }
         Node1(int key,int value,int freq){
             this.key=key;
             this.value=value;
             this.freq=freq;
         }
    }
    class DoublyLinkedList{
         Node1 head,tail;
         int size;
         DoublyLinkedList(){
             head=new Node1();
             tail=new Node1();
             head.next=tail;
             tail.pre=head;
             size=0;
         }
         public void addFirst(Node1 node1){
             Node1 pre=head.next;
             pre.pre=node1;
             node1.next=pre;
             head.next=node1;
             node1.pre=head;
             size++;
         }
         public void remove(Node1 node1){
             Node1 pre=node1.pre,next=node1.next;
             pre.next=next;
             next.pre=pre;
             size--;
         }
         public Node1 getHead(){
             return head.next;
         }
         public Node1 getTail(){
             return tail.pre;
         }
    }
/**
 * Your LFUCache object will be instantiated and called as such:
 * LFUCache obj = new LFUCache(capacity);
 * int param_1 = obj.get(key);
 * obj.put(key,value);
 */
//leetcode submit region end(Prohibit modification and deletion)

}
